
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using ScrewTurn.Wiki.PluginFramework;

namespace ScrewTurn.Wiki {

	/// <summary>
	/// Loads providers from assemblies.
	/// </summary>
	public static class ProviderLoader {

		private const string UsersProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IUsersStorageProvider";
		private const string PagesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IPagesStorageProvider";
		private const string FormatterProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFormatterProvider";

		/// <summary>
		/// Tries to inizialize a provider.
		/// </summary>
		/// <typeparam name="T">The type of the provider, which must implement <b>IProvider</b>.</typeparam>
		/// <param name="instance">The provider instance to initialize.</param>
		/// <param name="collectorEnabled">The collector for enabled providers.</param>
		/// <param name="collectorDisabled">The collector for disabled providers.</param>
		private static void Initialize<T>(T instance, ProviderCollector<T> collectorEnabled,
			ProviderCollector<T> collectorDisabled) where T : class, IProvider {

			if(collectorEnabled.GetProvider(instance.GetType().ToString()) != null ||
					collectorDisabled.GetProvider(instance.GetType().ToString()) != null) {
				Log.LogEntry("Provider " + instance.Information.Name + " already in memory", EntryType.Warning, "SYSTEM");
				return;
			}

			bool enabled = !IsDisabled(instance.GetType().ToString());
			try {
				if(enabled) {
					instance.Init(Host.Instance, LoadConfiguration(instance.GetType().ToString()));
				}
			}
			catch(InvalidConfigurationException) {
				// Disable Provider
				enabled = false;
				Log.LogEntry("Unable to load provider " + instance.Information.Name + " (configuration refused), disabling it", EntryType.Error, "SYSTEM");
				SaveStatus(instance.GetType().ToString(), false);
			}
			catch {
				// Disable Provider
				enabled = false;
				Log.LogEntry("Unable to load provider " + instance.Information.Name + " (unknown error), disabling it", EntryType.Error, "SYSTEM");
				SaveStatus(instance.GetType().ToString(), false);
				throw; // Exception is rethrown because it's not a normal condition
			}
			if(enabled) collectorEnabled.AddProvider(instance);
			else collectorDisabled.AddProvider(instance);
			Log.LogEntry("Provider " + instance.Information.Name + " loaded (" + (enabled ? "Enabled" : "Disabled") + ")", EntryType.General, "SYSTEM");
		}

		/// <summary>
		/// Loads all the Providers and initialises them.
		/// </summary>
		public static void FullLoad() {
			string[] files = Directory.GetFiles(Settings.PluginsDirectory, "*.dll");

			List<IUsersStorageProvider> users = new List<IUsersStorageProvider>();
			List<IUsersStorageProvider> dUsers = new List<IUsersStorageProvider>();
			List<IPagesStorageProvider> pages = new List<IPagesStorageProvider>();
			List<IPagesStorageProvider> dPages = new List<IPagesStorageProvider>();
			List<IFormatterProvider> forms = new List<IFormatterProvider>();
			List<IFormatterProvider> dForms = new List<IFormatterProvider>();

			for(int i = 0; i < files.Length; i++) {
				IUsersStorageProvider[] u;
				IPagesStorageProvider[] p;
				IFormatterProvider[] f;
				LoadFrom(files[i], out u, out p, out f);
				users.AddRange(u);
				pages.AddRange(p);
				forms.AddRange(f);
			}

			// Init and add to the Collectors
			for(int i = 0; i < users.Count; i++) {
				Initialize<IUsersStorageProvider>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
			}

			for(int i = 0; i < pages.Count; i++) {
				Initialize<IPagesStorageProvider>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
			}

			for(int i = 0; i < forms.Count; i++) {
				Initialize<IFormatterProvider>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
			}
		}

		/// <summary>
		/// Loads the Configuration data of a Provider.
		/// </summary>
		/// <param name="typeName">The Type Name of the Provider.</param>
		/// <returns>The Configuration, if available, otherwise an empty string.</returns>
		public static string LoadConfiguration(string typeName) {
			if(File.Exists(Settings.PluginsConfigurationDirectory + typeName + ".cs")) {
				return Tools.LoadFile(Settings.PluginsConfigurationDirectory + typeName + ".cs");
			}
			else return "";
		}

		/// <summary>
		/// Saves the Configuration data of a Provider.
		/// </summary>
		/// <param name="typeName">The Type Name of the Provider.</param>
		/// <param name="config">The Configuration data to save.</param>
		public static void SaveConfiguration(string typeName, string config) {
			Tools.WriteFile(Settings.PluginsConfigurationDirectory + typeName + ".cs", config);
		}

		/// <summary>
		/// Saves the Status of a Provider.
		/// </summary>
		/// <param name="typeName">The Type Name of the Provider.</param>
		/// <param name="enabled">A value specifying whether or not the Provider is enabled.</param>
		public static void SaveStatus(string typeName, bool enabled) {
			string data = Tools.LoadFile(Settings.PluginsStatusFile).Replace("\r", "");
			string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			int idx = -1;
			for(int i = 0; i < lines.Length; i++) {
				if(lines[i].Equals(typeName)) {
					idx = i;
					break;
				}
			}
			if(enabled) {
				if(idx >= 0) {
					StringBuilder sb = new StringBuilder();
					for(int i = 0; i < lines.Length; i++) {
						if(i != idx) sb.Append(lines[i] + "\r\n");
					}
					Tools.WriteFile(Settings.PluginsStatusFile, sb.ToString());
				}
				// Else nothing to do
			}
			else {
				if(idx == -1) {
					StringBuilder sb = new StringBuilder();
					for(int i = 0; i < lines.Length; i++) {
						if(i != idx) sb.Append(lines[i] + "\r\n");
					}
					sb.Append(typeName + "\r\n");
					Tools.WriteFile(Settings.PluginsStatusFile, sb.ToString());
				}
				// Else nothing to do
			}
		}

		/// <summary>
		/// Returns a value specifying whether or not a Provider is disabled.
		/// </summary>
		/// <param name="typeName">The Type Name of the Provider.</param>
		/// <returns>True if the Provider is disabled.</returns>
		public static bool IsDisabled(string typeName) {
			string data = Tools.LoadFile(Settings.PluginsStatusFile).Replace("\r", "");
			string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			for(int i = 0; i < lines.Length; i++) {
				if(lines[i].Equals(typeName)) return true;
			}
			return false;
		}

		/// <summary>
		/// Loads Providers from an assembly.
		/// </summary>
		/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
		public static void LoadFromAuto(string assembly) {
			IUsersStorageProvider[] users;
			IPagesStorageProvider[] pages;
			IFormatterProvider[] forms;
			LoadFrom(assembly, out users, out pages, out forms);

			// Init and add to the Collectors
			for(int i = 0; i < users.Length; i++) {
				Initialize<IUsersStorageProvider>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
			}

			for(int i = 0; i < pages.Length; i++) {
				Initialize<IPagesStorageProvider>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
			}

			for(int i = 0; i < forms.Length; i++) {
				Initialize<IFormatterProvider>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
			}

		}

		/// <summary>
		/// Loads Providers from an assembly.
		/// </summary>
		/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
		/// <param name="users">The Users Providers.</param>
		/// <param name="pages">The Pages Providers.</param>
		/// <param name="formatters">The Formatter Providers.</param>
		/// <remarks>The Components returned are <b>not</b> initialized.</remarks>
		public static void LoadFrom(string assembly, out IUsersStorageProvider[] users, out IPagesStorageProvider[] pages, out IFormatterProvider[] formatters) {
			Assembly asm = null;
			try {
				//asm = Assembly.LoadFile(assembly);
				// This way the DLL is not locked and can be deleted at runtime
				asm = Assembly.Load(LoadAssembly(assembly));
			}
			catch {
				users = new IUsersStorageProvider[0];
				pages = new IPagesStorageProvider[0];
				formatters = new IFormatterProvider[0];
				Log.LogEntry("Unable to load assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, "SYSTEM");
				return;
			}
			Type[] types = asm.GetTypes();

			List<IUsersStorageProvider> urs = new List<IUsersStorageProvider>();
			List<IPagesStorageProvider> pgs = new List<IPagesStorageProvider>();
			List<IFormatterProvider> frs = new List<IFormatterProvider>();

			Type[] interfaces;
			for(int i = 0; i < types.Length; i++) {
				// Avoid to load abstract classes as they cannot be instantiated
				if(types[i].IsAbstract) continue;

				interfaces = types[i].GetInterfaces();
				for(int k = 0; k < interfaces.Length; k++) {
					switch(interfaces[k].ToString()) {
						case UsersProviderInterfaceName:
							IUsersStorageProvider tmpu = CreateInstance<IUsersStorageProvider>(asm, types[i]);
							if(tmpu != null) urs.Add(tmpu);
							break;
						case PagesProviderInterfaceName:
							IPagesStorageProvider tmpp = CreateInstance<IPagesStorageProvider>(asm, types[i]);
							if(tmpp != null) pgs.Add(tmpp);
							break;
						case FormatterProviderInterfaceName:
							IFormatterProvider tmpf = CreateInstance<IFormatterProvider>(asm, types[i]);
							if(tmpf != null) frs.Add(tmpf);
							break;
					}
				}
			}

			users = urs.ToArray();
			pages = pgs.ToArray();
			formatters = frs.ToArray();
		}

		/// <summary>
		/// Creates an instance of a type implementing a provider interface.
		/// </summary>
		/// <typeparam name="T">The provider interface type.</typeparam>
		/// <param name="asm">The assembly that contains the type.</param>
		/// <param name="type">The type to create an instance of.</param>
		/// <returns>The instance, or <b>null</b>.</returns>
		private static T CreateInstance<T>(Assembly asm, Type type) where T : class, IProvider {
			T instance;
			try {
				instance = asm.CreateInstance(type.ToString()) as T;
				return instance;
			}
			catch {
				Log.LogEntry("Unable to create instance of " + type.ToString(), EntryType.Error, "SYSTEM");
				throw;
			}
		}

		/// <summary>
		/// Loads the content of an assembly.
		/// </summary>
		/// <param name="assembly">The assembly file path.</param>
		/// <returns>The content of the assembly, in a byte array form.</returns>
		private static byte[] LoadAssembly(string assembly) {
			byte[] result;
			FileStream fs = new FileStream(assembly, FileMode.Open, FileAccess.Read, FileShare.Read);
			MemoryStream ms = new MemoryStream();
			int read = 0;
			byte[] buff = new byte[512];
			do {
				read = fs.Read(buff, 0, buff.Length);
				ms.Write(buff, 0, read);
			} while(read > 0);
			fs.Close(); // Important
			result = ms.ToArray();
			ms.Close(); // Important
			return result;
		}

	}

}
